import java.util.*;
import javax.swing.*;
import java.awt.*;

public class Part4 {
	
	/**
	 * Creates a random matrix with upper and lower bounds
	 * @param width The width of the matrix 
	 * @param height The height of the matrix
	 * @param lowerBound The lower bound of the values of the matrix
	 * @param upperBound The upper bound of the values of the matrix
	 * @return The random matrix of specified dimension and range
	 */
	public double[][] createRandomMatrix(int width, int height, double lowerBound, double upperBound)
	{
		double[][] randomMatrix = new double[height][width];
		
		Random generator = new Random();
		
		for (int i = 0; i < height; i++)
		{
			for (int j = 0; j < width; j++)
				randomMatrix[i][j] = lowerBound + generator.nextDouble() * (upperBound - lowerBound);
		}
		
		System.out.println();
		printMatrix(randomMatrix);
		System.out.println();

		
		return randomMatrix;
	}
	
	/**
	 * Estimates the max eigen value of a 2x2 matrix with power method
	 * @param m1 The matrix
	 * @return An estimate of the max eigen value and the iterations it took to get there in a 2d array
	 * where the first element is the eigen value and the second element is the iterations
	 */
	public double[] powerMethod(double[][] m1)
	{
		double[][] powerEigen = new double[2][1];
		double[] realEigen = eigenValues2x2(m1);
		double accuracy = 0.000001;
		int iterations = 0;
		
		//determine max eigenvalue to compare to (this method calculates larger eigen value)
		double thisEigenValue;
		if (realEigen[0] > realEigen[1])
			thisEigenValue = realEigen[0];
		else
			thisEigenValue = realEigen[1];
		
		//set the intial powerEigen values
		powerEigen[0][0] = 1;
		powerEigen[1][0] = 0;
			
		while(Math.abs(powerEigen[0][0]-thisEigenValue) > accuracy)
		{
			//uk+1 = 1/ alphak * A * Uk where alpha k is the first component of uk
			powerEigen = scalarMultiply(multiply(m1,powerEigen), 1/powerEigen[0][0]);
			
			if (iterations < 50)
				iterations++;
			else
				break;
			
		}
		
//		System.out.println("final estimate max eigen vectors");
//		printMatrix(powerEigen);
//		System.out.println(iterations);
		
		double[] returnVal = {powerEigen[0][0],iterations};
		return(returnVal);
	}
	
	/**
	 * Estimates the min eigen value of a 2x2 matrix with inverse power method
	 * @param m1 The matrix
	 * @return An estimate of the min eigen value and the iterations it took to get there in a 2d array
	 * where the first element is the eigen value and the second element is the iterations
	 */
	public double[] inversePowerMethod(double[][] m1)
	{
		double[][] powerEigen = new double[2][1];
		double[] realEigen = eigenValues2x2(m1);
		double[][] m1Inverse = inverse(m1);
		double accuracy = 0.000001;
		int iterations = 0;
		
		//determine min eigenvalue to compare to (this method calculates smaller eigen value)
		double thisEigenValue;
		if (realEigen[0] < realEigen[1])
			thisEigenValue = realEigen[0];
		else
			thisEigenValue = realEigen[1];
		
		//set the intial powerEigen values
		powerEigen[0][0] = 1;
		powerEigen[1][0] = 0;
			
		while(Math.abs(powerEigen[0][0]-thisEigenValue) > accuracy)
		{
			//uk+1 = 1/ alphak * A * Uk where alpha k is the first component of uk
			powerEigen = scalarMultiply(multiply(m1Inverse,powerEigen), 1/powerEigen[0][0]);
			
			if (iterations < 50)
				iterations++;
			else
				break;		}
		
//		System.out.println("final estimate min eigen vectors");
//		printMatrix(powerEigen);
//		System.out.println(iterations);
		
		double[] returnVal = {powerEigen[0][0],iterations};
		return(returnVal);
	}
	
	//TODO: GENERAL USE METHODS
	/**
	 * Prints the matrix to the screen
	 * @param m The matrix to be printed to the screen
	 */
	public void printMatrix(double[][] m)
	{
		for (int i = 0; i < m.length; i++)
		{
			for (int j = 0; j<m[0].length; j++)
			{
				System.out.print(m[i][j]);
				System.out.print(" ");
			}
			System.out.println();
		}

	}
	
	/**
	 * Multiplies two matrices together and returns the result. 
	 * NOTE: The matrix {{3,2},{1,4}} is the following:
	 * 3 2 
	 * 1 4
	 * @param m1 The first matrix
	 * @param m2 The second matrix
	 * @return The resulting matrix
	 */
	public double[][] multiply(double[][] m1, double[][] m2)
	{
		double[][] result = new double[m1.length][m2[0].length];
		
		  for(int i = 0; i < m1.length; i++) 
			  for(int j = 0; j < m2[0].length; j++) 
		    	  for(int k = 0; k < m1[0].length; k++)
		    		  result[i][j] += m1[i][k]*m2[k][j];
		  
		return result;
	}
	
	/**
	 * Multiplies the matrix by a scalar
	 * @param m1 The matrix
	 * @param val A scalar
	 * @return The resulting matrix
	 */
	public double[][] scalarMultiply(double[][] m1, double val)
	{
		double[][] result = new double[m1.length][m1[0].length];
		
		  for(int i = 0; i < m1.length; i++) 
			  for(int j = 0; j < m1[0].length; j++) 
		    		 result[i][j] += m1[i][j]*val;
		  
		return result;
	}
	
	/**
	 * Creates and identity matrix of dimension n
	 * @param n The dimensions of the identity matrix
	 * @return The identity matrix
	 */
	public double[][] createIdentity(int n)
	{
		double[][] identity = new double[n][n];
		
		for (int i = 0; i < n; i++)
			for (int j = 0; j< n; j++)
				if (j == i)
					identity[i][j] = 1;
				else
					identity[i][j] = 0;
		
		return identity;
	}
	
	public double[][] createOnesMatrix(int n)
	{
		double[][] ones = new double[n][1];
		
		for (int i = 0; i < n; i++)
				ones[i][0] = 1;
		
		return ones;

	}
	
	public double[][] add(double[][] m1, double[][] m2)
	{
		double[][] add = new double[m1.length][m1.length];
		
		for (int i = 0; i < m1.length; i++)
			for (int j = 0; j< m1.length; j++)
				add[i][j] = m1[i][j] + m2[i][j];
		
		return add;
	}
	
	public double[][] subtract(double[][] m1, double[][] m2)
	{
		double[][] add = new double[m1.length][m1.length];
		
		for (int i = 0; i < m1.length; i++)
			for (int j = 0; j< m1.length; j++)
				add[i][j] = m1[i][j] - m2[i][j];
		
		return add;
	}
	
	public double[][] transpose(double[][] m1)
	{
		double[][] transpose = new double[m1[0].length][m1.length];
		
		for (int i = 0; i < m1[0].length; i++)
			for (int j = 0; j< m1.length; j++)
				transpose[i][j] = m1[j][i];
		
		return transpose;
	}
	
	/**
	 * Returns the determinant of a two by two matrix
	 * @param m1 A two by two matrix
	 * @return The determinant of the matrix
	 */
	public double determinant2x2(double[][] m1)
	{
		if (m1.length != 2 || m1[0].length != 2)
			return -1;
		
		return m1[0][0]*m1[1][1] - m1[1][0] * m1[0][1];
	}
	
	/**
	 * Returns the trace (the sum of the elements on the main diagonal) of a matrix
	 * @param m1 The matrix to find the trace of
	 * @return The trace of the matrix
	 */
	public double trace(double[][] m1)
	{
		double trace = 0;
		
		for (int i = 0; i < m1.length; i++)
			for (int j = 0; j < m1[0].length; j++)
				if (j == i)
					trace += m1[i][j];
		
		return trace;
	}
	
	/**
	 * Returns the norm of a matrix
	 * @param m1 The matrix to work on
	 * @return The norm aka length
	 */
	public double norm(double[][] m1)
	{		
		double tempTotal = 0;
		
		for (int i = 0; i < m1.length; i++)
		{
			tempTotal+=Math.pow(m1[i][0],2);
		}
		
		return Math.sqrt(tempTotal);
	}
	
	/**
	 * Calculates the eigen values of a 2x2 matrix
	 * @param m1 A 2x2 matrix
	 * @return A 2 element array containing the eigen values 
	 */
	public double[] eigenValues2x2(double[][] m1)
	{
		double[] eigenValues = new double[2];
		
		//use quadratic formula
		//the equation settup is as follows
		//lamba^2 - ([0][0]+[1][1])lambda + [0][0]*[1][1] - [0][1]*[1][0]
		
		eigenValues[0] = (m1[0][0] + m1[1][1] + Math.sqrt(Math.pow(m1[0][0] + m1[1][1],2) - 4 * (1) * (m1[0][0]*m1[1][1] - m1[0][1]*m1[1][0])))/2;
		eigenValues[1] = (m1[0][0] + m1[1][1] - Math.sqrt(Math.pow(m1[0][0] + m1[1][1],2) - 4 * (1) * (m1[0][0]*m1[1][1] - m1[0][1]*m1[1][0])))/2;

//		System.out.println(eigenValues[0]);
//		System.out.println(eigenValues[1]);

		return eigenValues;


	}
	
	public double[][] inverse(double[][] m1)
	{
		return scalarMultiply(m1, 1 / (determinant2x2(m1)));
	}
	
	public void solveProblem()
	{
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Grapher panel = new Grapher();
		frame.add(panel);
		
		frame.pack();
		frame.setVisible(true);
	}
	
	public static void main(String[] args)
	{
		Part4 test = new Part4();
		
		double[][] m1 = {{4,-5},{2,-3}};
		double[][] m2 = {{1,1},{0,-1}};
		
		double[][] m0 = m2;
		
		for (int i = 0; i < 25; i++)
			test.createRandomMatrix(2,2,-2,2);
		
		System.out.println("max eigen value:");
		System.out.println(test.powerMethod(m0));
		System.out.println("min eigen value:");
		System.out.println(test.inversePowerMethod(m0));
		System.out.println("eigen values:");
		System.out.println((test.eigenValues2x2(m0)[0]));
		System.out.println((test.eigenValues2x2(m0)[1]));
		
		test.solveProblem();
		
	}
	
	private class Grapher extends JPanel
	{
		public Grapher()
		{
			setPreferredSize(new Dimension(1000,1000));
		}
		
		public void paint(Graphics g)
		{
			int circleSize = 10;
			int scaleFactor = 50;
			Color dotColor;
			
			for (int i = 0; i < 10000; i++)
			{
				double[][] temp = createRandomMatrix(2,2,-2,2);
				double[] lolz = inversePowerMethod(temp);
				int colorVal = (int)(255 * (powerMethod(temp)[1]/30));
				if (colorVal > 255)
					colorVal = 255;
				//dotColor = new Color(255-(int)(255 * (powerMethod(temp)[1]/50)),128, 0+(int)(255 * (powerMethod(temp)[1]/50))); //second element of method is iterations
				dotColor = new Color((int)(255 * (powerMethod(temp)[1]/50)),0, 0); //second element of method is iterations
				g.setColor(dotColor);
				int x = (int)(determinant2x2(temp)*scaleFactor)-circleSize/2;
				g.fillOval((int)(determinant2x2(temp)*scaleFactor)-circleSize/2+scaleFactor*10, (int)(trace(temp)*scaleFactor)-circleSize/2+scaleFactor*10, circleSize, circleSize);
				System.out.println(lolz[1]);
				System.out.println(dotColor);
			}
			
			g.setColor(Color.blue);
			g.drawLine(0+scaleFactor*10, 0, 0+scaleFactor*10, 1000);
			g.drawLine(0, 0+scaleFactor*10, 1000, 0+scaleFactor*10);
			g.drawString("Determinant", 0, 0+scaleFactor*10);
			g.drawString("Trace", 0+scaleFactor*10, 32);


			System.out.println("I DREW");

		}
	}
}
